package model

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"log"
	"os"
	"runtime/debug"
	"sync"
	"time"
)

// 定义全局的db对象，我们执行数据库操作主要通过他实现。
var (
	_db  *gorm.DB
	dbMu sync.Mutex // 用于锁住重连过程，防止多线程重复重连
)

func init() {
	connectMysql()
	startDBHealthMonitor() // 启动后台健康检查协程
}

func connectMysql() {

	//username := "root"  //账号    //
	//password := "root"  //密码
	//host := "127.0.0.1" //数据库地址，可以是Ip或者域名
	//port := 3306        //数据库端口
	//Dbname := "scum"    //数据库名
	//timeout := "120s"   //连接超时，10秒

	//配置MySQL连接参数

	username := "scum"        //账号
	password := "scum123456"  //密码
	host := "150.158.130.144" //数据库地址，可以是Ip或者域名
	port := 3306              //数据库端口
	Dbname := "scum"          //数据库名
	timeout := "120s"         //连接超时，10秒

	//拼接下dsn参数, dsn格式可以参考上面的语法，这里使用Sprintf动态拼接dsn参数，因为一般数据库连接参数，我们都是保存在配置文件里面，需要从配置文件加载参数，然后拼接dsn。
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local&timeout=%s", username, password, host, port, Dbname, timeout)

	// 声明err变量，下面不能使用:=赋值运算符，否则_db变量会当成局部变量，导致外部无法访问_db变量

	//连接MYSQL, 获得DB类型实例，用于后面的数据库读写操作。
	// 获取当前日期
	// 设置日志目录路径
	// 确保日志目录存在
	logDir := "runtime/log/mysql"
	err := os.MkdirAll(logDir, 0755)
	if err != nil {
		log.Fatalf("无法创建日志目录: %v\n", err)
	}

	// 获取当前日期
	today := time.Now().Format("2006-01-02")          // 格式：YYYY-MM-DD
	logFileName := logDir + "/" + today + "-gorm.log" // 日志文件名按日期命名

	// 设置日志文件路径
	logFile, err := os.OpenFile(logFileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
	if err != nil {
		log.Fatalf("无法创建日志文件: %v\n", err)
	}
	defer logFile.Close()

	// 创建自定义的日志器，将日志写入文件
	newLogger := logger.New(
		log.New(logFile, "\r\n", log.LstdFlags),
		logger.Config{
			SlowThreshold: time.Second * 1, // 慢 SQL 阈值
			LogLevel:      logger.Info,     // 日志级别
			Colorful:      false,           // 启用彩色打印
		},
	)
	_db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: newLogger})
	if err != nil {
		log.Printf("数据库连接失败: %v\n", err)
		panic("连接数据库失败, error=" + err.Error())
	}
	fmt.Println("数据库连接成功")
	sqlDB, _ := _db.DB()

	//设置数据库连接池参数
	sqlDB.SetMaxOpenConns(100) //设置数据库连接池最大连接数
	sqlDB.SetMaxIdleConns(50)  //连接池最大允许的空闲连接数，如果没有sql任务需要执行的连接数大于20，超过的连接会被连接池关闭。
	sqlDB.SetConnMaxLifetime(3600 * time.Second)

}

// 获取gorm db对象，其他包需要执行数据库查询的时候，只要通过tools.getDB()获取db对象即可。
// 不用担心协程并发使用同样的db对象会共用同一个连接，db对象在调用他的方法的时候会从数据库连接池中获取新的连接

// 自动重连函数（加锁保护）
func reconnectDB() {
	dbMu.Lock()
	defer dbMu.Unlock()

	log.Println("检测到数据库连接失效，正在尝试重连...")
	connectMysql()
	log.Println("数据库重连完成")
}

// 获取数据库连接，自动检查 + 重连
func GetDB() *gorm.DB {
	defer func() {
		if r := recover(); r != nil {

			fmt.Println("获取数据库连接失败", r)
			fmt.Println("获取数据库连接失败", string(debug.Stack()))
		}

	}()
	sqlDB, err := _db.DB()
	if err != nil || sqlDB.Ping() != nil {
		log.Printf("数据库连接失效: %v，执行自动重连", err)
		reconnectDB()
	}
	return _db
}

// 获取数据库连接，自动检查 + 重连
//func GetDB() *gorm.DB {
//	sqlDB, err := _db.DB()
//	if err != nil || sqlDB.Ping() != nil {
//		log.Printf("数据库连接失效: %v，执行自动重连", err)
//		reconnectDB()
//	}
//	return _db
//}

// 后台定时健康检查
func startDBHealthMonitor() {
	go func() {
		defer func() {
			if r := recover(); r != nil {

				fmt.Println("mysql健康检查错误", r)
				fmt.Println("mysql健康检查错误", string(debug.Stack()))
			}

		}()
		for {
			time.Sleep(30 * time.Second) // 每 30 秒检查一次
			sqlDB, err := _db.DB()
			if err != nil || sqlDB.Ping() != nil {
				log.Println("定时健康检查发现数据库连接失效")
				reconnectDB()
			}
		}
	}()
}
